package com.sxhuayuan.parking.controller.open;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.sxhuayuan.parking.compenent.Message;
import com.sxhuayuan.parking.config.CarActType;
import com.sxhuayuan.parking.controller.open.req.ParkAuthcode2openidData;
import com.sxhuayuan.parking.controller.open.req.ParkCarOrdersData;
import com.sxhuayuan.parking.controller.open.req.ParkInNoticeData;
import com.sxhuayuan.parking.controller.open.req.ParkInQueryData;
import com.sxhuayuan.parking.controller.open.req.ParkOutFinishData;
import com.sxhuayuan.parking.controller.open.req.ParkOutNoticeData;
import com.sxhuayuan.parking.controller.open.req.ParkQueryIsPassableData;
import com.sxhuayuan.parking.controller.open.resp.ParkCarOrdersResp;
import com.sxhuayuan.parking.controller.open.resp.ParkInQueryResp;
import com.sxhuayuan.parking.controller.open.resp.ParkInResp;
import com.sxhuayuan.parking.controller.open.resp.ParkOutNoticeResp;
import com.sxhuayuan.parking.controller.open.resp.ParkQueryIsPassableResp;
import com.sxhuayuan.parking.entity.Order;
import com.sxhuayuan.parking.entity.ParkChannel;
import com.sxhuayuan.parking.entity.ParkingIn;
import com.sxhuayuan.parking.entity.ParkingLot;
import com.sxhuayuan.parking.exception.MyException;
import com.sxhuayuan.parking.exception.ParkException;
import com.sxhuayuan.parking.plugin.parking.bluecard.request.PayChannel;
import com.sxhuayuan.parking.plugin.parking.bluecard.result.BluecardCharge;
import com.sxhuayuan.parking.plugin.parking.bluecard.result.BluecardResult;
import com.sxhuayuan.parking.plugin.parking.bluecard.result.BluecardResultData;
import com.sxhuayuan.parking.plugin.payment.Authcode2openidResp;
import com.sxhuayuan.parking.plugin.payment.WxPayPlugin;
import com.sxhuayuan.parking.queue.QueueService;
import com.sxhuayuan.parking.service.ExitingCarService;
import com.sxhuayuan.parking.service.OrderService;
import com.sxhuayuan.parking.service.ParkChannelService;
import com.sxhuayuan.parking.service.ParkFeeCacheService;
import com.sxhuayuan.parking.service.ParkService;
import com.sxhuayuan.parking.service.ParkingInOutService;
import com.sxhuayuan.parking.service.ParkingInService;
import com.sxhuayuan.parking.service.ParkingLotService;
import com.sxhuayuan.parking.service.PartnerpayService;
import com.sxhuayuan.parking.service.PluginService;
import com.sxhuayuan.parking.service.RedisService;
import com.sxhuayuan.parking.service.impl.pojo.ParkInNoticeResult;
import com.sxhuayuan.parking.service.impl.pojo.ParkPreOutNoticeResult;
import com.sxhuayuan.parking.utils.DateUtils;
import com.sxhuayuan.parking.utils.Md5Utils;
import com.sxhuayuan.parking.utils.SettingUtils;
import com.sxhuayuan.parking.utils.SignatureUtils;

/**
 * 通用车场接口
 * 
 * @author gy
 */
@RestController("openApiController")
@RequestMapping("/open/api")
public class ApiController extends BaseController {

	Logger log = LoggerFactory.getLogger(getClass());

	@Autowired
	ParkingLotService lotService;
	@Autowired
	PartnerpayService partnerpayService;
	@Autowired
	ParkChannelService exitService;
	@Autowired
	OrderService orderService;
	@Autowired
	ParkingInService inService;
	@Autowired
	ParkingInOutService parkingInOutService;
	@Autowired
	ParkFeeCacheService parkFeeCacheService;
	@Autowired
	PluginService pluginService;
	@Resource(name = "rabbitMsgQueue")
	QueueService rabbitMsgQueue;
	@Resource(name = "wxPayPlugin")
	WxPayPlugin wxPayPlugin;
	@Autowired
	RedisService redisService;
	@Autowired
	ExitingCarService exitingCarService;
	@Autowired
	ParkService parkService;

	final String SIGN = "sign";
	final String TIMESTAMP = "timestamp";
	final String NONCE = "nonce";
	final String METHOD = "method";
	final String DATA = "data";
	final String APPID = "appid";

	@RequestMapping(value = "/100/gateway", method = RequestMethod.POST)
	public ApiResp gateway(HttpServletRequest request, HttpServletResponse response) {
		String uuid = RandomStringUtils.randomAlphanumeric(8);
		String mime = request.getContentType();
		if (mime == null || !mime.contains("application/json")) {
			response.setStatus(HttpStatus.SC_FORBIDDEN);
			return null;
		}
		String token = null;
		try {
			String json = IOUtils.toString(request.getInputStream(), "utf-8");
			log.debug("[{}]request:{}", uuid, json);
			JSONObject reqJson = JSON.parseObject(json, Feature.OrderedField);
			if (reqJson == null || !reqJson.containsKey(SIGN)) {
				return ApiResp.error("Param sign is missing");
			}
			if (!reqJson.containsKey(TIMESTAMP)) {
				return ApiResp.error("Param timestamp is missing");
			}
			long timeDiff = Math.abs(System.currentTimeMillis() - reqJson.getLongValue(TIMESTAMP));
			if (timeDiff > 5 * 60 * 1000) {
				return ApiResp.error("Param timestamp is timeout");
			}
			if (!reqJson.containsKey(NONCE)) {
				return ApiResp.error("Param nonce is missing");
			}
			int length = reqJson.getString(NONCE).length();
			if (length < 8 || length > 16) {
				return ApiResp.error("Length of param nonce is invalid");
			}
			if (!reqJson.containsKey(METHOD)) {
				return ApiResp.error("Param method is missing");
			}
			ParkingLot lot = lotService.findBySn(reqJson.getString(APPID));
			if (lot == null) {
				return ApiResp.error("Param appid is invalid");
			}
			if (!checkRequestSign(reqJson, json, lot.getApiKey())) {
				return ApiResp.error("Sign is invalid");
			}
			// 缓存签名token，用于统一异常时签名
			token = lot.getApiKey();

			switch (reqJson.getString(METHOD)) {

			// 通知车辆入场
			case "park.in.notice": {
				ParkInNoticeData data = JSON.parseObject(reqJson.getString(DATA), ParkInNoticeData.class);
				if (!isValid(data)) {
					return getValidErrorInfo();
				}
				ParkInNoticeResult parkInNoticeResult = parkService.parkInNotice(lot, data);
				ParkInResp respData = new ParkInResp();
				respData.setUniqueId(parkInNoticeResult.getUniqueId());
				respData.setCarPlate(parkInNoticeResult.getCarPlate());
				return ApiResp.success(respData).sign(token);
			}
			// 根据车牌查找入场记录
			case "park.in.query": {
				return parkInQuery(reqJson, lot, token);
			}
			case "park.out.notice": {
				ParkOutNoticeData data = JSON.parseObject(reqJson.getString(DATA), ParkOutNoticeData.class);
				if (!isValid(data)) {
					return getValidErrorInfo();
				}
				if (StringUtils.isEmpty(data.getChannel()) && StringUtils.isEmpty(data.getDevice())) {
					return ApiResp.error("channel和device必须2选1");
				}
				try {
					ParkPreOutNoticeResult parkOutNoticeResult = parkService.preOutNotice(lot, data);
					ParkOutNoticeResp respData = new ParkOutNoticeResp(String.valueOf(parkOutNoticeResult.getUniqueId()));
					if (parkOutNoticeResult.getIsPassable()) {
						respData.passableYes();
						return ApiResp.success(respData).sign(token);
					} else {
						respData.passableNo();
						return ApiResp.success(parkOutNoticeResult.getMsg(), respData).sign(token);
					}
				} catch (ParkException e) {
					return ApiResp.error(e.getMessage());
				}
			}
			// 查询车辆缴费记录
			case "park.car.orders": {
				return parkCarOrders(reqJson, lot, token);
			}
			case "park.out.finish": {
				return parkOutFinish(reqJson, lot, token);
			}
			// 查询车辆缴费状态，用uniqueid
			case "park.query.passable": {
				ParkQueryIsPassableData data = JSON.parseObject(reqJson.getString(DATA), ParkQueryIsPassableData.class);
				ParkingIn in = inService.findByUniqueId(Long.valueOf(data.getUniqueId()));
				if (in == null) {
					return ApiResp.success(Message.CAR_NO_ENTRANCE_RECORD).sign(token);
				}
				ParkQueryIsPassableResp resp = new ParkQueryIsPassableResp(String.valueOf(in.getUniqueId()));
				if (in.getLastChargeTime() == null && DateUtils.diff(in.getInTime(), data.getOutTime(), Calendar.MINUTE) <= data.getFreeMinutes()) {
					log.info("未超过免费停车时间:{}", data.getFreeMinutes());
					resp.passableYes();
					// resp.setParkFee(0f);
				} else if (in.getLastChargeTime() != null && DateUtils.diff(in.getLastChargeTime(), data.getOutTime(), Calendar.MINUTE) < data.getFreeMinutesForLeaving()) {
					log.info("已扫码缴费");
					resp.passableYes();
					// resp.setParkFee(0f);
					resp.addOrders(orderService.findByUniqueId(Long.valueOf(data.getUniqueId())));
				} else {
					log.info("未缴费: {}", in.getCarPlate());
					// resp.setParkFee(parkFeeCacheService.get(in.getUniqueId()).getTotalFee());
				}
				return ApiResp.success(resp).sign(token);
			}
			// 微信authcode换取openid
			case "park.wx.getopenid": {
				ParkAuthcode2openidData data = JSON.parseObject(reqJson.getString(DATA), ParkAuthcode2openidData.class);
				if (!isValid(data)) {
					return getValidErrorInfo();
				}
				Authcode2openidResp result = wxPayPlugin.authcode2openid(lot, data.getAuthcode());
				return ApiResp.success(result).sign(token);
			}
			default: {
				return ApiResp.error("Param method is invalid!");
			}
			}
		} catch (MyException e) {
			log.error("操作失败：" + e.getMessage());
			return ApiResp.error(e.getMessage());
		} catch (IOException e) {
			log.error("[{}]", uuid, e);
			return ApiResp.error("read request body error! ");
		} catch (Exception e) {
			log.error("[{}]", uuid, e);
			return ApiResp.error("请求失败，错误: " + e.getMessage());
		}
	}

	/**
	 * 蓝卡专用 出口触发算费通知
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping(value = "/bluecard/outNotify/{sn}", method = RequestMethod.POST)
	public String outNotify(HttpServletRequest request, HttpServletResponse response, @PathVariable String sn) {
		String uuid = RandomStringUtils.randomAlphanumeric(8);
		String mime = request.getContentType();
		log.debug("[{}]content-type={}", uuid, mime);
		if (!"application/json".equalsIgnoreCase(mime)) {
			response.setStatus(HttpStatus.SC_FORBIDDEN);
			return null;
		}
		String json;
		try {
			json = IOUtils.toString(request.getInputStream(), "utf-8");
		} catch (IOException e) {
			log.error("[{}]", uuid, e);
			return BluecardResult.failJson("read request body error! ");
		}
		log.debug("[{}]request={}", uuid, json);
		JSONObject data = JSON.parseObject(json);
		String sign = request.getHeader("sign");
		if (data == null || sign == null || "".equals(sign)) {
			return BluecardResult.failJson("Param sign is missing");
		}
		Date timestamp = DateUtils.parse(data.getString(TIMESTAMP));
		long timeDiff = Math.abs(System.currentTimeMillis() - timestamp.getTime());
		if (timeDiff > 5 * 60 * 1000) {
			return BluecardResult.failJson("Param timestamp is timeout");
		}
		ParkingLot lot = lotService.findBySn(sn);
		if (lot == null) {
			return BluecardResult.failJson("Param appid is invalid");
		}
		// 验证签名
		if (!sign.equals(Md5Utils.md5(lot.getApiKey() + data.getString("parkNumber")))) {
			return BluecardResult.failJson("Sign is invalid");
		}

		// 判断redis中是否有重复的数据 如果又则出场
		String carPlate = data.getString("plate");
		String channelName = data.getString("outChannel");
		ParkChannel channel = exitService.findByParkingLotAndTypeAndName(lot, ParkChannel.Type.exit, channelName);
		if (channel == null)
			return BluecardResult.failJson("未找到对应车道");
		String channelSn = channel.getSn();
		if (exitingCarService.hasKey(channelSn))
			exitingCarService.remove(channelSn);
		Float payCharge = data.getFloat("payCharge");
		// 线判断费用是否未0
		if (payCharge == null) {
			return BluecardResult.failJson("应收金额不存在");
		}
		ParkingIn in = inService.findByCarPlate(lot.getId(), carPlate);
		if (in == null) {
			Date inTime = DateUtils.parse(data.getString("inTime"));
			in = inService.enter(lot, carPlate, inTime, channel.getId(), lot.getFreeMinutes(), data.getString("orderId"), null);
		}
		log.debug("carPlate = {}, in= {}", carPlate, JSONObject.toJSONString(in));
		// 如果应付金额为0 (蓝卡技术人员说这种情况不会推送,线写着)
		if (payCharge.floatValue() == 0) {
			// 根据 uniqueId 找到指定订单 如果找不到 返回一个空对象
			BluecardResultData bdata = uniqueIdToBluecardResultData(in.getUniqueId());
			log.debug("应付金额为0, 预支付成功, 直接通过");
			return BluecardResult.successJsonWriteNull(bdata);
		}
		/* 封装数据 */
		// 调用执行无感 和 执行save redis 方法
		ParkOutNoticeData parkData = new ParkOutNoticeData();
		parkData.setCarPlate(carPlate);
		parkData.setUniqueId(String.valueOf(in.getUniqueId()));
		parkData.setChannel(channel.getSn());
		// 设备编号
		parkData.setDevice(channel.getDeviceId());
		String time = DateUtils.format(DateUtils.parse(data.getString("outTime")), DateUtils.YYYYMMDDHHMMSS);
		parkData.setTime(time);
		parkData.setFee(data.getFloat("payCharge"));
		parkData.setDiscount(data.getFloat("profitCharge"));
		// 入场免费分钟数? = 累计优惠时长
		parkData.setFreeMinutes(lot.getFreeMinutes());
		// 免费离场时间分钟数 暂时写死
		parkData.setFreeMinutesForLeaving(lot.getFreeMinutesForLeaving());
		Integer parkTime = (int) Math.ceil((DateUtils.parse(data.getString("outTime")).getTime() - in.getInTime().getTime()) / (60 * 1000));
		parkData.setParkMinutes(parkTime);
		parkData.setLotOrderNo(data.getString("outTradeNo"));
		parkData.setLotRecordId(data.getString("orderId"));
		ParkPreOutNoticeResult pr = null;
		try {
			// 方法内判断 -> 免费时长 -> 无感 -> 如果都不放行 save redis 如果放行 需要完成个出场动作
			pr = parkService.preOutNotice(lot, parkData, false, false);
			log.debug("[{}]pr = {}", uuid, JSONObject.toJSONString(pr));
		} catch (ParkException e) {
			log.error("[{}]", uuid, e);
			return BluecardResult.failJson(e.getMessage());
		}
		/* 无感支付失败, 返回失败, 不抬杆, 缴费信息 存入redis */
		if (!pr.getIsPassable()) {
			return BluecardResult.failJson("未缴费");
		}
		/* 无感支付成功, 返回成功, 抬杆 */
		log.debug("无感支付成功, 抬杆");
		return BluecardResult.successJsonWriteNull(uniqueIdToBluecardResultData(in.getUniqueId()));
	}

	/**
	 * 根据 传入的uniqueid 填充返回对象 BluecardResultData 如果找不到订单 返回空对象
	 * 
	 * @param uniqueId
	 * @return
	 */
	private BluecardResultData uniqueIdToBluecardResultData(Long uniqueId) {
		List<Order> orderList = orderService.findByUniqueId(uniqueId);
		if (orderList == null || orderList.size() == 0) {
			BluecardCharge bCharge = new BluecardCharge();
			BluecardResultData bdata = new BluecardResultData(new ArrayList<BluecardCharge>());
			bdata.getChargeList().add(bCharge);
			bdata.setTimeStamp(String.valueOf(System.currentTimeMillis()));
			return bdata;
		}
		Float orderPayCharge = 0f;
		String payKind = "";
		List<BluecardCharge> chargeList = new ArrayList<BluecardCharge>();
		for (Order order : orderList) {
			String endTime = DateUtils.format(order.getEndtime());
			// 应收金额(已缴金额)
			Float money = order.getMoney().floatValue();
			orderPayCharge = SettingUtils.get().setScale(BigDecimal.valueOf((orderPayCharge + money))).floatValue();
			// 支付渠道
			String payChannelName = PayChannel.getByName(order.getMethod().name()).getCname();

			BluecardCharge bCharge = new BluecardCharge(order.getSn(), endTime, String.valueOf(money), payKind, payChannelName, "");
			chargeList.add(bCharge);
		}
		return new BluecardResultData(String.valueOf(orderPayCharge), String.valueOf(orderPayCharge), payKind, String.valueOf(System.currentTimeMillis()), chargeList);

	}

	private ApiResp parkInQuery(JSONObject request, ParkingLot lot, String token) {
		ParkInQueryData data = JSON.parseObject(request.getString(DATA), ParkInQueryData.class);
		if (!isValid(data)) {
			return getValidErrorInfo();
		}
		ParkingIn in = inService.findByCarPlate(lot.getId(), data.getCarPlate());
		if (in == null) {
			return ApiResp.error(Message.CAR_NO_ENTRANCE_RECORD);
		} else {
			ParkInQueryResp respData = new ParkInQueryResp();
			respData.setInTime(in.getInTime());
			respData.setUniqueId(String.valueOf(in.getUniqueId()));
			respData.setCarPlate(in.getCarPlate());
			respData.setCardNo(in.getCardNo());
			respData.setLastChargeTime(in.getLastChargeTime());
			List<Order> orders = orderService.findPaidListByUniqueId(in.getUniqueId());
			respData.addOrders(orders);
			return ApiResp.success(respData).sign(token);
		}
	}

	private ApiResp parkCarOrders(JSONObject request, ParkingLot lot, String token) {
		ParkCarOrdersData data = JSON.parseObject(request.getString(DATA), ParkCarOrdersData.class);
		if (StringUtils.isEmpty(data.getCarPlate()) && StringUtils.isEmpty(data.getUniqueId())) {
			return ApiResp.error("uniqueId和carPlate不能同时为空");
		}
		ParkingIn in = null;
		if (!StringUtils.isEmpty(data.getUniqueId())) {
			in = inService.findByUniqueId(Long.valueOf(data.getUniqueId()));
		} else {
			in = inService.findByCarPlate(lot.getId(), data.getCarPlate());
		}
		if (in == null) {
			return ApiResp.error(Message.CAR_NO_ENTRANCE_RECORD);
		}
		List<Order> orders = orderService.findByUniqueId(in.getUniqueId());
		ParkCarOrdersResp resp = new ParkCarOrdersResp();
		resp.setLastPayTime(in.getLastChargeTime());
		resp.setInTime(in.getInTime());
		resp.addOrders(orders);
		return ApiResp.success(resp).sign(token);
	}

	/**
	 * 完成出场，车主可能使用现金支付，此时后台需要完成本次入场
	 */
	private ApiResp parkOutFinish(JSONObject request, ParkingLot lot, String token) {
		ParkOutFinishData data = JSON.parseObject(request.getString(DATA), ParkOutFinishData.class);
		if (!isValid(data)) {
			return getValidErrorInfo();
		}
		ParkingIn in = inService.findByCarPlate(lot.getId(), data.getCarPlate());
		if (in != null) {
			ParkChannel exit = null;
			if (!StringUtils.isEmpty(data.getChannel())) {
				exit = exitService.findBySn(data.getChannel());
			} else if (!StringUtils.isEmpty(data.getDevice())) {
				exit = exitService.findByDevice(lot.getId(), data.getDevice());
			}
			try {
				log.debug("[{}]其他支付方式出场", data.getCarPlate());
				Long exitId = (exit != null ? exit.getId() : null);
				parkingInOutService.out(in, data.getTime(), exitId);
			} catch (Exception e) {
				log.error("", e);
				return ApiResp.error(Message.COMMON_UNKNOWN_ERROR);
			}
		} else {
			return ApiResp.error(Message.CAR_NO_ENTRANCE_RECORD);
		}
		return ApiResp.success().sign(token);
	}

	private boolean checkRequestSign(JSONObject jo, String jstr, String key) {

		Map<String, Object> map = new HashMap<>();
		Set<String> keySet = jo.keySet();
		for (String k : keySet) {
			map.put(k, jo.getString(k));
		}
		map.remove(SIGN);
		String sign = SignatureUtils.signMd5(map, "key", key);
		return sign.equalsIgnoreCase(jo.getString(SIGN));
	}

	@RequestMapping(value = "/print")
	public ApiResp print(HttpServletRequest request, HttpServletResponse response) {
		String contentType = request.getContentType();
		if (contentType == null || "application/x-www-form-urlencoded".equals(contentType)) {
			Enumeration<String> parameterNames = request.getParameterNames();
			while (parameterNames.hasMoreElements()) {
				String name = parameterNames.nextElement();
				log.debug("{} = {}", name, request.getParameter(name));
			}
		} else {
			try {
				log.debug(IOUtils.toString(request.getInputStream(), "utf-8"));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return ApiResp.success();
	}

}
